package com.zhuiyi.bizlog.annotation;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import com.zhuiyi.bizlog.beans.LogRecord;
import com.zhuiyi.bizlog.beans.LogRecordOps;
import com.zhuiyi.bizlog.parse.LogRecordValueParser;
import com.zhuiyi.bizlog.service.ILogRecordService;
import com.zhuiyi.bizlog.service.IOperatorGetService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author wanglinzhi0916
 * @Description TODO
 * @Date 2021/3/8 11:04
 **/
@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    @Resource
    private ILogRecordService bizLogService;
    @Resource
    private LogRecordValueParser logRecordValueParser;
    @Resource
    private IOperatorGetService operatorGetService;

    @Pointcut("@annotation(com.zhuiyi.bizlog.annotation.LogRecordAnnotation)")
    public void controllerAspect() {

    }

    @Around("controllerAspect()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        LogRecordAnnotation logAnnotation = method.getAnnotation(LogRecordAnnotation.class);
        Object[] args = point.getArgs();
        Class<?> targetClass = point.getThis().getClass();
        Object ret = null;
        boolean success = true;
        String errorMsg = "";
        Throwable throwable = null;
        try {
             ret = point.proceed();
        } catch (Exception e) {
            success = false;
            errorMsg = e.getMessage();
            throwable = e;
        }
        try {
            Collection<LogRecordOps> operations = new ArrayList<>(1);
            operations.add(parseLogRecordAnnotation(logAnnotation));
            if (!CollectionUtils.isEmpty(operations)) {
                recordExecute(ret, method, args, operations, targetClass, success, errorMsg);
            }
        } catch (Exception e) {
            //记录日志错误不要影响业务
            log.error("记录操作日志错误,方法为：" + method.getName() + "----" + e);
        } finally {
            if (throwable != null) {
                throw throwable;
            }
            return ret;
        }
    }

    private void recordExecute(Object ret, Method method, Object[] args, Collection<LogRecordOps> operations,
                               Class<?> targetClass, boolean success, String errorMsg) {
        for (LogRecordOps operation : operations) {
            try {
                String action = "";
                if (success) {
                    action = operation.getSuccessLogTemplate();
                } else {
                    action = operation.getFailLogTemplate();
                    if (StringUtils.isEmpty(action)) {
                        //执行失败，并且没有配失败日志模版则忽略
                        continue;
                    }
                }
                String bizKey = operation.getBizKey();
                String bizNo = operation.getBizNo();
                String operatorId = operation.getOperatorId();
                String category = operation.getCategory();
                String detail = operation.getDetail();
                //获取需要解析的表达式
                List<String> spElTemplates;
                String realOperator = "";
                if (StringUtils.isEmpty(operatorId)) {
                    spElTemplates = Lists.newArrayList(bizKey, bizNo, action, detail);
                    if (operatorGetService.getUser() == null || StringUtils.isEmpty(operatorGetService.getUser().getOperatorId())) {
                        throw new IllegalArgumentException("user is null");
                    }
                } else {
                    spElTemplates = Lists.newArrayList(bizKey, bizNo, action, operatorId, detail);
                }
                Map<String, String> expressionValues = logRecordValueParser.processTemplate(spElTemplates, ret, targetClass, method, args, errorMsg);

                LogRecord logRecord = LogRecord.builder()
                        .bizKey(expressionValues.get(bizKey))
                        .bizNo(expressionValues.get(bizNo))
                        .operator(!StringUtils.isEmpty(realOperator) ? realOperator : expressionValues.get(operatorId))
                        .category(category)
                        .detail(expressionValues.get(detail))
                        .action(expressionValues.get(action))
                        .createTime(new Date())
                        .build();

                //save log 需要新开事务，失败日志不能因为事务回滚而丢失
                Preconditions.checkNotNull(bizLogService, "bizLogService not init!!");
                bizLogService.record(logRecord);
            } catch (Exception t) {
                log.error("log record execute exception", t);
            }
        }

    }
    private LogRecordOps parseLogRecordAnnotation(LogRecordAnnotation recordAnnotation) {
        LogRecordOps recordOps = LogRecordOps.builder()
                .successLogTemplate(recordAnnotation.success())
                .failLogTemplate(recordAnnotation.fail())
                .bizKey(recordAnnotation.prefix().concat("_").concat(recordAnnotation.bizNo()))
                .bizNo(recordAnnotation.bizNo())
                .operatorId(recordAnnotation.operatorId())
                .category(StringUtils.isEmpty(recordAnnotation.category()) ? recordAnnotation.prefix() : recordAnnotation.category())
                .detail(recordAnnotation.detail())
                .build();
        validateLogRecordOperation(recordOps);
        return recordOps;
    }
    private void validateLogRecordOperation( LogRecordOps recordOps) {
        if (!StringUtils.hasText(recordOps.getSuccessLogTemplate()) && !StringUtils.hasText(recordOps.getFailLogTemplate())) {
            throw new IllegalStateException("Invalid logRecord annotation configuration on '" +
                     "'. 'one of successTemplate and failLogTemplate' attribute must be set.");
        }
    }
}
